﻿/* -----------------------------------------------------------------------------
 * Copyright (c) 2019-2021 linling
 *
 * 本软件"按原样"提供，没有任何明示或暗示的保证。 在任何情况下，作者均
 * 不对因使用本软件而导致的任何损害承担责任。 允许任何人将本软件用于
 * 任何目的（包括商业应用程序），并对其进行更改并自由重新分发，但受以下限制：
 *
 * 1.不得歪曲本软件的来源; 你不能声称你写了原始软件。
 *   如果你在产品中使用此软件，在产品文档中的承认将不胜感激，但不是必需的。
 *
 * 2.更改的源版本必须明确标记，不得歪曲为原始软件。
 *
 * 3.不得从任何来源分发中删除或更改此通知。
 */
#ifndef __MODBUS_RTU_H
#define __MODBUS_RTU_H

#include "mbed.h"
#include "xos2.h" 
#include "api.h" 

#ifndef __NO_MODBUS_TEST_CODE
//#define MODBUS_TEST_CODE
#endif

class Modbus {
public:
	int Baudrate;
	qSerialPort  SerialPort;
	DigitalOut   rs485ctrl;
	bool DebugEnable;
#ifdef MODBUS_TEST_CODE
	int LastReadResponseTime;
	int LastWriteResponseTime;
	int LastReadError;
	int LastWriteError;
#endif // MODBUS_TEST_CODE

	Modbus(PinName tx, PinName rx, PinName ctrl) :SerialPort(tx, rx), rs485ctrl(ctrl), DebugEnable(false)
#ifdef MODBUS_TEST_CODE
		,LastReadResponseTime(0), LastWriteResponseTime(0),
		LastReadError(0), LastWriteError(0)
#endif // MODBUS_TEST_CODE
	{
	}
	void rs485send(byte* buf, int len, cstr_t msg);
	bool ReadReg(int mdaddr, int regaddr, int cnt, byte* rbuf);
	bool WriteReg(int mdaddr, int regaddr, int data);
	bool WriteReg(int mdaddr, int regaddr, int cnt, byte* wbuf, byte* buf = NULL);
	void SetBaudrate(int b) {
		Baudrate = b;
		SerialPort.baud(b);
	}
protected:
	void printf(cstr_t msg, byte* buf, int len);
};

class ModbusServer :public Modbus {
public:
	uint16* Regs;
	int Cnt;
	byte NodeAddr;
	byte CmdAddr;
#ifdef MODBUS_TEST_CODE
	int LastReadResponseTimeMax;
	int LastWriteResponseTimeMax;
#endif // MODBUS_TEST_CODE
	int IntervalTime;

	ModbusServer(PinName tx, PinName rx, PinName ctrl) :Modbus(tx, rx, ctrl), 
		IntervalTime(1) {
		SerialPort.rxQueue.Init(512);
		Regs = NULL;
		Cnt = 0;
		tout_mark = true;
		NodeAddr = 0;
		rxcnt = 0;
		stat = 0;
	}
	void InitServer(byte na, uint16* regs, int cnt) {
		NodeAddr = na;
		Regs = regs;
		Cnt = cnt;
	}
	void InitServer(byte na, int cnt) {
		if (cnt == 0) {
			return;
		}
		NodeAddr = na;
		Cnt = cnt;
		Regs = new uint16[cnt];
	}
	void SetNodeAddress(byte na) {
		NodeAddr = na;
	}
	void SetCmdAddress(byte na) {
		CmdAddr = na;
	}
	
	/*
		读PLC保持寄存器，读多个
		@param mdaddr  站点地址
		@param regaddr 寄存器首地址
		@param cnt  表示寄存器数据，不是字节数，最大127个寄存器
		@param rbuf 用于存放读取到的内容，长度应该=cnt
		@return
			0  读取成功并通过CRC校验
			-1 超时或CRC校验失败
			-2 寄存器太长
	*/
	int ReadRegArray(int regaddr, int cnt, uint16* rbuf, int mdaddr = -1) {
		int l = 0;
		int rcnt = 0;
		int r;
#ifdef MODBUS_TEST_CODE
		int t = 0;
		//LastReadResponseTimeMax = 0;
#endif // MODBUS_TEST_CODE
		if (mdaddr == -1) mdaddr = CmdAddr;
		do {
			l = MIN(96, cnt - rcnt);
			r = ReadRegEx(regaddr + rcnt, l, rbuf + rcnt, mdaddr);
			if (r != 0)break;
#ifdef MODBUS_TEST_CODE
			t += LastReadResponseTime;
			if (LastReadResponseTime > LastReadResponseTimeMax) LastReadResponseTimeMax = LastReadResponseTime;
#endif // MODBUS_TEST_CODE
			rcnt += l;
			if (IntervalTime > 0)xos::Wait(IntervalTime);
		} while (cnt > rcnt);
#ifdef MODBUS_TEST_CODE
		LastReadResponseTime = t;
#endif // MODBUS_TEST_CODE
		return r;
	}
	int ReadRegEx(int regaddr, int cnt, uint16* rbuf, int mdaddr = -1) {
		if (cnt > 127) {
			return -2;
		}
		int x = ReadReg(mdaddr, regaddr, cnt, xbuf);
		if (x != 0) {
			for (int i = 0; i < cnt; i++) {
				rbuf[i] = xbuf[3 + i * 2] * 256 + xbuf[4 + i * 2];
			}
		}
		return -(1 - x);
	}

	/*
		写PLC保持寄存器，写单个
		@param mdaddr  站点地址
		@param regaddr 寄存器地址
		@param data    要写入的数据
		@return
			0  写入成功，反馈信息通过CRC校验
			-1 失败
	*/
	int WriteRegEx(int mdaddr, int regaddr, uint16 data) {
		return -(1 - WriteReg(mdaddr, regaddr, data));
	}

	/*
	写PLC保持寄存器，写多个
	@param mdaddr  站点地址
	@param regaddr 寄存器首地址
	@param cnt  表示寄存器数据，不是字节数，最大127个寄存器
	@param wbuf 将要写入的内容，长度=cnt,是小端格式
	@return
		0  写入成功，反馈信息通过CRC校验
		-1 失败
		-2 寄存器太长
	*/
	int WriteRegArray(int regaddr, int cnt, uint16* wbuf, int mdaddr = -1) {
		int l = 0;
		int wcnt = 0;
		int r;
#ifdef MODBUS_TEST_CODE
		int t = 0;
		//LastWriteResponseTimeMax = 0;
#endif // MODBUS_TEST_CODE
		do {
			l = MIN(96, cnt - wcnt);
			r = WriteRegEx(regaddr + wcnt, l, wbuf + wcnt, mdaddr);
			if (r != 0)break;
#ifdef MODBUS_TEST_CODE
			t += LastWriteResponseTime;
			if (LastWriteResponseTime > LastWriteResponseTimeMax) LastWriteResponseTimeMax = LastWriteResponseTime;
#endif // MODBUS_TEST_CODE
			wcnt += l;
			if (IntervalTime > 0)xos::Wait(IntervalTime);
		} while (cnt > wcnt);
#ifdef MODBUS_TEST_CODE
		LastWriteResponseTime = t;
#endif // MODBUS_TEST_CODE
		return r;
	}
	int WriteRegEx(int regaddr, int cnt, uint16* wbuf, int mdaddr = -1) {
		if (cnt > 127) {
			return -2;
		}
		if (mdaddr == -1) mdaddr = CmdAddr;
		return -(1 - WriteReg(mdaddr, regaddr, cnt, (byte*)wbuf, xbuf));
	}

	void Task();
private:
	byte xbuf[512];
	byte rxbuf[512];
	TimeLoop t1;
	int rxcnt;
	bool tout_mark;
	byte stat;
	void clear() {
		tout_mark = true;
		rxcnt = 0;
		stat = 0;
	}
};
#endif
